package nlppatents.util;

import java.util.StringTokenizer;
import java.util.ArrayList;

public class Sentence 
{
	/**
	 * Takes a raw tagged sentence (string) and returns a Sentence object.
	 * A sentence object is essentially a list of constituent phrases (Phrase objects),
	 * whose word elements are Word's (object).
	 * @param s The sentence to parse
	 * @param incFreq  boolean; if true, increment word frequencies as they are encountered
	 * @param patent optional (pass null); used to count the number of patents a word has appeared in
	 * @return A Sentence object if successful; null if not successful.
	 */
	public static Sentence parseRawSentence(String s, boolean incFreq, Patent patent)
	{
		StringTokenizer t = new StringTokenizer(s);
		if(!t.hasMoreTokens()) return null;
		
		// push all tokens into an array list making phrase formation easier
		ArrayList<POSWord> words = new ArrayList<POSWord>(t.countTokens());
		while(t.hasMoreTokens())
		{
			POSWord w = POSWord.get(t.nextToken());
			
			//L.add(t.nextToken());
			if(w != null)
			{
				words.add(w);
				if(incFreq)
				{
					w.incFreq();
					
					// NOTE: EXPERIMENTAL
					/*if(w.dictWord.lastPatent != patent)
					{
						w.dictWord.lastPatent = patent;
						w.dictWord.patentCount++;
					}*/
				}
			}
		}
		
		Sentence sentence = new Sentence();
		int len = words.size();
		int index = 0;
		int phraseLength = 0;
		
		Phrase phrase = null;
		
		/*
		 * obvious guilty confession:  There's tons of copy-paste here, and I fully intend to 
		 * re-code this section later.  For now, though, this works adequately
		 */
		while(index < len)
		{
			POS p = words.get(index).pos;
			
			if(p == POS.cc)
			{
				// only 1 strategy for CC's: immediately emit them as their own phrase
				phrase = new Phrase(POS.cc);
				
				while(p == POS.cc) // looping should handle any weird case where we would otherwise lose some juicy np's
				{
					phrase.add(words.get(index));
					sentence.add(phrase);
					
					index++;
					if(index < len)
						p = words.get(index).pos;
					else
						p = null;
					//System.out.println("phraseLength: ?");
				}
			}
			else if(p == POS.det)
			{
				phraseLength = isNP(words, index);
				if(phraseLength == 0)
				{
					// weird case, but anything is possible with the magic of NLP
					phrase = new Phrase(POS.word);
					phrase.add(words.get(index));
					sentence.add(phrase);
					
					index++;
					//System.out.println("phraseLength: " + phraseLength);
				}
				else
				{
					phrase = new Phrase(POS.noun);
					for(int i = 0; i < phraseLength; i++)
						phrase.add(words.get(index + i));
					sentence.add(phrase);
					
					index += phraseLength; // this will set to word immediately following phrase
					//System.out.println("phraseLength: " + phraseLength);
				}
			}
			else if(p == POS.prep)
			{
				phraseLength = isPP(words, index);
				if(phraseLength == 0)
				{
					phrase = new Phrase(POS.word);
					phrase.add(words.get(index));
					sentence.add(phrase);
					
					index++;
					//System.out.println("phraseLength: " + phraseLength);
				}
				else
				{
					phrase = new Phrase(POS.prep);
					for(int i = 0; i < phraseLength; i++)
						phrase.add(words.get(index + i));
					sentence.add(phrase);
					
					index += phraseLength; // this will set to word immediately following phrase
					//System.out.println("phraseLength: " + phraseLength);
				}
			}
			else if(p == POS.adj)
			{
				phraseLength = isNP(words, index);
				if(phraseLength == 0)
				{
					phrase = new Phrase(POS.word);
					phrase.add(words.get(index));
					sentence.add(phrase);
					
					index++;
					//System.out.println("phraseLength: " + phraseLength);
				}
				else 
				{
					phrase = new Phrase(POS.noun);
					for(int i = 0; i < phraseLength; i++)
						phrase.add(words.get(index + i));
					sentence.add(phrase);
					
					index += phraseLength; // this will set to word immediately following phrase
					//System.out.println("phraseLength: " + phraseLength);
				}
			}
			else if(p == POS.noun)
			{
				// isNP cannot fail from this point
				phraseLength = isNP(words, index);
				
				phrase = new Phrase(POS.noun);
				for(int i = 0; i < phraseLength; i++)
					phrase.add(words.get(index + i));
				sentence.add(phrase);
				
				index += phraseLength; // this will set to word immediately following phrase
				//System.out.println("phraseLength: " + phraseLength);
			}
			else if(p == POS.to)
			{
				if(index + 1 < len)
				{
					phraseLength = isNP(words, index+1);
					if(phraseLength > 0)
					{
						phrase = new Phrase(POS.noun);
					}
					else
					{
						phraseLength = isVP(words, index);
						phrase = new Phrase(POS.verb); // this might not be used if phraseLength is 0
						//System.out.println("to with verb, len is " + phraseLength);
					}
					if(phraseLength > 0)
					{
						for(int i = 0; i < phraseLength; i++)
							phrase.add(words.get(index + i));
						sentence.add(phrase);
						index += phraseLength;
						//System.out.println("phraseLength: " + phraseLength);
						continue;  // prevents default case
					}
				}

				// default case
				phrase = new Phrase(POS.word);
				phrase.add(words.get(index));
				sentence.add(phrase);
				
				index++;
				//System.out.println("phraseLength: " + 1);
			}
			else if(p == POS.verb)
			{
				// isVP can't fail
				phraseLength = isVP(words, index);
				
				phrase = new Phrase(POS.verb);
				for(int i = 0; i < phraseLength; i++)
					phrase.add(words.get(index + i));
				sentence.add(phrase);
				
				index += phraseLength; // this will set to word immediately following phrase
				//System.out.println("phraseLength: " + phraseLength);
			}
			else if(p == POS.ls)
			{
				phrase = new Phrase(POS.word);
				phrase.add(words.get(index));
				sentence.add(phrase);
				
				//System.out.println("phraseLength: 1");
				index++;
			}
			else if(p == POS.comma)
			{
				phrase = new Phrase(POS.comma);
				phrase.add(words.get(index));
				sentence.add(phrase);
				
				//System.out.println("phraseLength: 1");
				index++;
			}
			else if(p == POS.period)
			{
				phrase = new Phrase(POS.period);
				phrase.add(words.get(index));
				sentence.add(phrase);
				
				//System.out.println("phraseLength: 1");
				index++;
			}
			else
			{
				// default case
				phrase = new Phrase(POS.word);
				phrase.add(words.get(index));
				sentence.add(phrase);
				
				//System.out.println("phraseLength: " + 1);
				index++;
			}
		}
		
		return sentence;
	}
	
	/**
	 * Determines whether words[offset] is the begging of a Noun Phrase. If
	 * so, the number of words that are part of the noun phrase.  
	 * @return Number of words in NP, or 0 if not a valid NP
	 */
	public static int isNP(ArrayList<POSWord> words, int offset)
	{
		if(offset >= words.size()) return 0;
		
		int end = offset;
		
		// check for initial DT, if any
		if(words.get(end).pos == POS.det) end++;
		if(end >= words.size()) return 0;
		
		// TODO: error detection for verb -> adj
		
		POS p = words.get(end).pos;
		
		// ensure that this is a noun-phrase
		if((p != POS.adj) && (p != POS.noun)) return 0; // not a NP
		while((p == POS.adj) || (p == POS.noun))
		{
			end++;
			if(end >= words.size()) break;
			p = words.get(end).pos;
		}
		end--; // the black crow sings
		
		// ... the black crow, offset = 3, end = 5, length = 3
		return end - offset + 1;
	}
	
	public static int isVP(ArrayList<POSWord> words, int offset)
	{
		if(offset >= words.size()) return 0;
		int end = offset;
		
		// check for initial "to", if any
		if(words.get(end).pos == POS.to) end++;
		if(end >= words.size()) return 0;
		
		POS p = words.get(end).pos;
		if(p != POS.verb) return 0; // not a VP
		
		while((p == POS.verb) || (p == POS.to))
		{
			end++;
			if(end >= words.size()) break;
			p = words.get(end).pos;
		}
		end--;
		
		return end - offset + 1;
	}
	
	public static int isPP(ArrayList<POSWord> words, int offset)
	{
		if(offset >= words.size()) return 0;
		POS p = words.get(offset).pos;
		
		if((p != POS.prep) && (p != POS.to)) return 0;
		
		int length = isNP(words, offset+1);
		if(length == 0) return 0;
		
		return length + 1;
	}
////////////////////////////////////////////////////
////////////////// OBJECT SECTION //////////////////
////////////////////////////////////////////////////
	
	public ArrayList<Phrase> sentence;
	
	public Sentence()
	{
		sentence = new ArrayList<Phrase>();
	}
	
	public Phrase add(Phrase p)
	{
		if(p != null)
			sentence.add(p);
		return p;
	}
	
	// saves a few keystrokes
	public Phrase get(int index)
	{
		return sentence.get(index);
	}
	
	public int size()
	{
		return sentence.size();
	}
	
	public String toString()
	{
		// 4096 characters should be adequate in the general case thus ensuring speed
		StringBuilder builder = new StringBuilder(4096); 
		for(int p = 0; p < sentence.size(); p++) // phrase index
		{
			if(p != 0)
				builder.append('\t');  // tabs separate phrases
			
			Phrase phrase = sentence.get(p);
			// insert phrase type marker here
			builder.append(phrase.type.label);
			builder.append(": ");
			for(int w = 0; w < phrase.length(); w++) // word index
			{
				// note: this will generally not resolve to the original
				//builder.append(phrase.get(w).hashString);
				
				// experiment!  append only raw words
				builder.append(phrase.get(w).word);
				builder.append(' ');
			}
		}
		
		return builder.toString();
	}
	
	public void printFormatted()
	{
		for(int p = 0; p < sentence.size(); p++) // phrase index
		{
			Phrase phrase = sentence.get(p);
			System.out.print(phrase.type.label);
			System.out.print(": ");
			for(int w = 0; w < phrase.length(); w++) // word index
			{
				System.out.print(phrase.get(w).word);
				System.out.print(' ');
			}
			System.out.print('\n');
		}
	}

	/*
	 * For testing
	 */
	public static void main(String args[])
	{
		POS.init();
		POSWord.init();
		DictWord.init();
		
		String s0 = "Typically/RB ,/, the/DT host/NN PC/NN would/MD need/VB to/TO be/VB opened/VBN ,/, and/CC the/DT management/NN appliance/NN would/MD need/VB to/TO be/VB removed/VBN and/CC replaced/VBN ,/, or/CC at/IN least/JJS reconfigured/VBN ./.";
		String s1 = "It/PRP has/VBZ often/RB been/VBN extremely/RB difficult/JJ ,/, if/IN not/RB impossible/JJ ,/, to/TO reconfigure/VB the/DT monitored/JJ parameters/NNS of/IN an/DT industrial/JJ PC/NN from/IN a/DT remote/JJ administration/NN facility/NN ./.";
		String s2 = "Secondly/RB ,/, when/WRB the/DT main/JJ microprocessor/NN for/IN the/DT industrial/JJ PC/NN failed/VBD ,/, crashed/VBD ,/, or/CC otherwise/RB hung/VBD up/RP ,/, it/PRP was/VBD then/RB nearly/RB impossible/JJ to/TO determine/VB information/NN about/IN the/DT reason/NN for/IN the/DT crash/NN if/IN such/JJ information/NN was/VBD dependent/JJ ,/, in/IN some/DT way/NN ,/, upon/IN that/DT main/JJ microprocessor/NN for/IN reporting/NN ,/, etc/FW ./.";
	
		Sentence sen0 = Sentence.parseRawSentence(s0, true, null);
		Sentence sen1 = Sentence.parseRawSentence(s1, true, null);
		Sentence sen2 = Sentence.parseRawSentence(s2, true, null);
		
		System.out.println("Sentence 0: ");
		System.out.println(sen0);
		
		System.out.println("\nSentence 1: ");
		System.out.println(sen1);
		
		System.out.println("\nSentence 2: ");
		System.out.println(sen2);
	}
}